--- Java types needed to run processes.
{-- Because the associated classes are in @java.lang@,
    the stuff here would belong into package @frege.java.Lang@,
    however, it does not seem justified to have it imported in
    each and every program. -}
module frege.java.lang.Processes where


{--
     The type 'Process' provides methods for performing input 
     from the process, performing output to the process, 
     waiting for the process to complete, 
     checking the exit status of the process, 
     and destroying (killing) the process.
     
     An instance of 'Process' can be obtained from the 'Process.exec'
     or 'ProcessBuilder.start' methods.
-}
data  Process = native java.lang.Process where
    
    {--
        > Process.exec cmdarray envp dir
        > Process.exec "command"
        
        Executes the specified command and arguments in a separate 
        process with the specified environment and working directory.

        Given an array of strings _cmdarray_, 
        representing the tokens of a command line, 
        and an array of strings _envp_, 
        representing "environment" variable settings, 
        this method creates a new process in which to execute the specified command.

        This method checks that _cmdarray_ is a valid operating system command. 
        Which commands are valid is system-dependent, 
        but at the very least the command must be a non-empty list of non-null strings.

        If _envp_ is 'Nothing', 
        the subprocess inherits the environment settings of the current process.

        A minimal set of system dependent environment variables may be 
        required to start a process on some operating systems. 
        As a result, the subprocess may inherit additional environment 
        variable settings beyond those in the specified environment.

        The working directory of the new subprocess is specified by _dir_. 
        If _dir_ is 'Nothing', the subprocess inherits the 
        current working directory of the current process.

        If a security manager exists, its @checkExec@ method is invoked 
        with the first component of the array cmdarray as its argument. 
        This may result in a 'SecurityException' being thrown.

        Starting an operating system process is highly system-dependent. 
        Among the many things that can go wrong are:

            - The operating system program file was not found.
            - Access to the program file was denied.
            - The working directory does not exist.
            
        In such cases an exception will be thrown. 
        The exact nature of the exception is system-dependent, 
        but it will always be a subclass of 'IOException'.

        [Returns:] A new Process object for managing the subprocess
        [Throws:]
            - 'SecurityException' - If a security manager exists and its checkExec method doesn't allow creation of the subprocess
            - 'IOException' - If an I/O error occurs
            - 'NullPointerException' - If one of the elements of the passed arrays is null
            - 'IndexOutOfBoundsException' - If _cmdarray_ is an empty array (has length 0)
    -}
    native exec "java.lang.Runtime.getRuntime().exec" 
            :: ArrayOf RealWorld String             -- command 
               -> Maybe (ArrayOf RealWorld String)  -- environment
               -> Maybe File                        -- working directory
               -> IOMutable Process                        -- result
                throws IOException               
            |  String -> IOMutable Process                 -- poor mans exec
                throws IOException
                
    --- Kills the subprocess. The subprocess represented by this 'Process' object is forcibly terminated.
    native destroy :: MutableIO Process -> IO ()
    
    --- Returns the exit value for the subprocess represented by this Process object. 
    --- By convention, the value 0 indicates normal termination.
    native exitValue :: MutableIO Process -> IO Int
                        throws IllegalThreadStateException
    
    {-- 
        Causes the current thread to wait, if necessary, 
        until the process represented by this 'Process' object has terminated. 
        This method returns immediately if the subprocess has already terminated. 
        If the subprocess has not yet terminated, 
        the calling thread will be blocked until the subprocess exits.
        
        [Returns:] the exit value of the subprocess represented 
        by this 'Process' object. 
        By convention, the value 0 indicates normal termination.
        [Throws:] 'InterruptedException' if the current 'Thread' is
        interrupted by another 'Thread' while it is waiting.
    -}
    native waitFor :: MutableIO Process -> IO Int throws InterruptedException
    
    {-- 
        Returns the output stream connected to the 
        standard input of the subprocess. 
        Output to the stream is piped into the standard input 
        of the process represented by this 'Process' object.
        
        If the standard input of the subprocess has been redirected 
        using 'ProcessBuilder.redirectInput' then this method will 
        return an output stream for which the 'OutputStream.write'
        method always throws 'IOException' and the 'OutputStream.close'
        method does nothing. 
    -}
    native getOutputStream :: MutableIO Process -> IOMutable OutputStream
    
    {--
        Returns the input stream connected to the standard output 
        of the subprocess. 
        The stream obtains data piped from the standard output 
        of the process represented by this 'Process' object.
        
        If the standard output of the subprocess has been redirected 
        using 'ProcessBuilder.redirectOutput' then this method will 
        return an input stream, for which the 'InputStream.read' 
        method always returns @-1@, the 'InputStream.available'
        method returns always @0@ and the 'InputStream.close' method 
        does nothing.

        Otherwise, if the standard error of the subprocess has been 
        redirected using 'ProcessBuilder.redirectErrorStream'
        then the input stream returned by this method will receive 
        the merged standard output and the standard error of the subprocess.
    -}
    native getInputStream :: MutableIO Process -> IOMutable InputStream
    
    {--
        Returns the input stream connected to the standard error 
        output of the subprocess. 
        The stream obtains data piped from the error output 
        of the process represented by this 'Process' object.
        
        If the standard error of the subprocess has been redirected 
        using 'ProcessBuilder.redirectError' or 
        'ProcessBuilder.redirectErrorStream' then this method will 
        return an input stream, for which the 'InputStream.read' 
        method always returns @-1@, the 'InputStream.available'
        method returns always @0@ and the 'InputStream.close' method 
        does nothing.
    -}
    native getErrorStream :: MutableIO Process -> IOMutable InputStream


{-- 
    Convenience function to get an UTF-8 encoded 'PrintWriter' 
    that is connected to the standard input of a 'Process'.
-}
stdinWriter :: MutableIO Process -> IOMutable PrintWriter
stdinWriter p = do
    os <- p.getOutputStream
    bw <- OutputStreamWriter.new os "UTF-8"
    PrintWriter.new bw


{-- 
    Convenience function to get an UTF-8 encoded 'BufferedReader' 
    that is connected to the standard output of a 'Process'.
-}
stdoutReader :: MutableIO Process -> IOMutable BufferedReader
stdoutReader p = do
    is <- p.getInputStream
    ir <- InputStreamReader.new is "UTF-8"
    BufferedReader.new ir
    

{-- 
    Convenience function to get an UTF-8 encoded 'BufferedReader' 
    that is connected to the standard error of a 'Process'.
-}
stderrReader :: MutableIO Process -> IOMutable BufferedReader
stderrReader p = do
    is <- p.getErrorStream
    ir <- InputStreamReader.new is "UTF-8"
    BufferedReader.new ir
    
{--
    Represents a source of subprocess input or a destination of subprocess output. 
    Each 'Redirect' is one of the following:
    - the special value 'Redirect.pipe'
    - the special value 'Redirect.inherit'
    - a redirection to read from a file, created by an invocation of 'Redirect.from'
    - a redirection to write to a file, created by an invocation of 'Redirect.to'
    - a redirection to append to a file, created by an invocation of 'Redirect.appendTo'
-}
data Redirect = native java.lang.ProcessBuilder.Redirect where
    
    --- nowarn: We know this is constant
    --- Indicates that subprocess I/O will be connected to the current Java process over a pipe. 
    --- This is the default handling of subprocess standard I/O.
    native pipe    "java.lang.ProcessBuilder.Redirect.PIPE"    :: MutableIO Redirect
    
    --- nowarn: We know this is constant
    --- Indicates that subprocess I/O source or destination will be the same as those of the current process. 
    --- This is the normal behavior of most operating system command interpreters (shells).
    native inherit "java.lang.ProcessBuilder.Redirect.INHERIT" :: MutableIO Redirect
    
    --- Returns the 'File' source or destination associated with this redirect, or null if there is no such file.
    native file :: MutableIO Redirect -> IO (Maybe File)
    
    --- Redirect to read from the specified 'File'.
    native from java.lang.ProcessBuilder.Redirect.from :: File -> IOMutable Redirect
    
    --- Redirect to write to the specified file, discarding previous content, if any.
    native to java.lang.ProcessBuilder.Redirect.to :: File -> IOMutable Redirect
    
    --- Redirect to append to the specified file.
    --- Each write operation first advances the position to the end of the file and then writes the requested data.
    native appendTo java.lang.ProcessBuilder.Redirect.appendTo :: File -> IOMutable Redirect
      
{--
    The 'ProcessBuilder' type is used to create operating system processes.
    
    Each 'ProcessBuilder' manages a collection of process attributes. 
    The 'ProcessBuilder.start' method creates a new 'Process' instance 
    with those attributes. 
    The 'ProcessBuilder.start' method can be invoked repeatedly 
    from the same instance to create new subprocesses with 
    identical or related attributes.
    
    The following attributes are being managed:
    
    - a _command_, a list of strings which signifies the external 
    program file to be invoked and its arguments, if any. 
    Which string lists represent a valid operating system command is 
    system-dependent. For example, it is common for each conceptual 
    argument to be an element in this list, 
    but there are operating systems where programs are expected 
    to tokenize command line strings themselves - on such a system a 
    Java implementation might require commands to contain exactly 
    two elements.
    - an _environment_, which is a system-dependent mapping from 
    variables to values. The initial value is a copy of the 
    environment of the current process (see 'System.getenv').
    - a _working directory_. 
    The default value is the current working directory of 
    the current process, usually the directory named by the 
    system property @user.dir@.
    - a source of _standard input_. 
    By default, the subprocess reads input from a pipe. 
    Frege code can access this pipe via the output stream returned by 
    'Process.getOutputStream'. 
    However, standard input may be redirected to another source using 
    'ProcessBuilder.redirectInput'. In this case, 
    'Process.getOutputStream' will return an output stream, for which 
    the @write@ methods always throws 'IOException' 
    and the @close@ method does nothing.
    - a destination for _standard output_ and _standard error_. 
    By default, the subprocess writes standard output and 
    standard error to pipes. Frege code can access these pipes via the 
    input streams returned by 'Process.getInputStream' 
    and 'Process.getErrorStream'. 
    However, standard output and standard error may be redirected to 
    other destinations using 'ProcessBuilder.redirectOutput'
    and 'ProcessBuilder.redirectError'. 
    In this case, 'Process.getInputStream' and/or 'Process.getErrorStream'
    will return an input stream, 
    for which the @read@ methods always return @-1@,
    the @available@ method always returns @0@
    and the @close@ method does nothing.
    - a _redirectErrorStream_ property. 
    Initially, this property is false, meaning that the standard output 
    and error output of a subprocess are sent to two separate streams, 
    which can be accessed using the 'Process.getInputStream' 
    and 'Process.getErrorStream' methods.
    If the value is set to true, then standard error is merged 
    with the standard output and always sent to the same destination 
    (this makes it easier to correlate error messages with the corresponding output);
    the common destination of standard error and standard output 
    can be redirected using 'ProcessBuilder.redirectOutput';
    and any redirection set by the 'ProcessBuilder.redirectError'
    method is ignored when creating a subprocess.
    
    Modifying a process builder's attributes will affect 
    processes subsequently started by that object's start() method, 
    but will never affect previously started processes or the Java process itself.

    Most error checking is performed by the start() method. 
    It is possible to modify the state of an object so that start() will fail. 
    For example, setting the command attribute to an empty list 
    will not throw an exception unless start() is invoked.

    Note that this type is not synchronized. 
    If multiple threads access a 'ProcessBuilder' instance concurrently, 
    and at least one of the threads modifies one of the attributes structurally, 
    it must be synchronized externally.
-}    
data ProcessBuilder = native java.lang.ProcessBuilder where
    
    --- create a 'ProcessBuilder' passing the command line as array
    native newFromArray new :: ArrayOf RealWorld String -> IOMutable ProcessBuilder
    
    --- create a 'ProcessBuilder' passing the command line as list.
    new :: [String] -> IOMutable ProcessBuilder
    new xs = JArray.genericFromList xs >>= newFromArray
    
    ---  set the working directory
    native directory :: MutableIO ProcessBuilder -> File -> IOMutable ProcessBuilder
    
    --- inherit the standard input, output and error from the current process
    native inheritIO :: MutableIO ProcessBuilder -> IOMutable ProcessBuilder
    
    --- set the redirectErrorStream property
    native redirectErrorStream :: MutableIO ProcessBuilder -> Bool -> IOMutable ProcessBuilder
    
    --- start the new process
    --- For details see 'Process.exec'
    native start :: MutableIO ProcessBuilder -> IOMutable Process throws IOException
    
    --- redirect standard input
    native redirectInput 
            :: MutableIO ProcessBuilder -> MutableIO Redirect -> IOMutable ProcessBuilder
            |  MutableIO ProcessBuilder -> File -> IOMutable ProcessBuilder
    
    --- redirect standard output
    native redirectOutput 
            :: MutableIO ProcessBuilder -> MutableIO Redirect -> IOMutable ProcessBuilder
            |  MutableIO ProcessBuilder -> File -> IOMutable ProcessBuilder
    
    --- redirect standard error
    native redirectError 
            :: MutableIO ProcessBuilder -> MutableIO Redirect -> IOMutable ProcessBuilder
            |  MutableIO ProcessBuilder -> File -> IOMutable ProcessBuilder